﻿/***************
 * Copyright 2009 (C) 
 * Timur Fanshteyn
 * Blog: http://blog.tfanshteyn.com
 * ************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Tangosol.Util;
using Tangosol.Util.Filter;
using System.Collections;

namespace Coherence.Linq
{
    public class TranslateResult
    {
        public IFilter Filter {get;set;}
        public LambdaExpression Projector { get; set; }
    }

    public class CoherenceQueryTranslator : ExpressionVisitor
    {
        protected object globalFilter;
        private SelectionProjection projection;

        public CoherenceQueryTranslator()
        {

        }

        public TranslateResult Translate(Expression expression)
        {
            this.Visit(expression);
            return new TranslateResult() {
                Filter=(IFilter) globalFilter,
                Projector = this.projection != null ? 
                (LambdaExpression) this.projection.Selector: null };
        }

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable))
            {
                if (m.Method.Name == "Where")
                {
                    this.Visit(m.Arguments[0]);
                    object left = globalFilter;
                    LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
                    this.Visit(lambda.Body);
                    object right = globalFilter;
                    if (left != null)
                    {
                        globalFilter = new AndFilter((IFilter)left, (IFilter)right);
                    }
                    return m;
                }
                else if (m.Method.Name == "Select")
                {
                    LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
                    //ColumnProjection projection = new ColumnProjector().ProjectColumns(lambda.Body, this.row);                    
                    this.projection = new SelectionProjection() { Selector = lambda };
                    this.Visit(m.Arguments[0]);
                    return m;
                }
            }

            if (m.Method.DeclaringType == typeof(LinqFunctions))
            {
                if (m.Method.Name == "WhereOr")
                {
                    this.Visit(m.Arguments[0]);
                    object left = globalFilter;
                    LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
                    this.Visit(lambda.Body);
                    object right = globalFilter;
                    if (left != null)
                    {
                        globalFilter = new OrFilter((IFilter)left, (IFilter)right);
                    }
                    return m;
                }
                else if (m.Method.Name == "Like")
                {
                    this.Visit(m.Arguments[0]);
                    object left = globalFilter;
                    this.Visit(m.Arguments[1]);
                    string right = (string)globalFilter;
                    if (left is string)
                        globalFilter = new Tangosol.Util.Filter.LikeFilter((string) left, right);
                    else
                        globalFilter = new Tangosol.Util.Filter.LikeFilter((IValueExtractor) left, right, '\0', false);
                    return m;
                }
                else if (m.Method.Name == "In")
                {
                    this.Visit(m.Arguments[0]);
                    object left =  globalFilter;
                    this.Visit(m.Arguments[1]);
                    ICollection right = (ICollection)globalFilter;
                    if (left is string)
                        globalFilter = new Tangosol.Util.Filter.InFilter((string)left, right);
                    else
                        globalFilter = new Tangosol.Util.Filter.InFilter((IValueExtractor)left, right);
                    return m;
                }
                else if (m.Method.Name == "Extractor")
                {
                    this.Visit(m.Arguments[1]);
                    string right = (string)globalFilter;
                    globalFilter = new Tangosol.Util.Extractor.ReflectionExtractor(right);
                    return m;
                }
                else if (m.Method.Name == "ChainedExtractor")
                {
                    this.Visit(m.Arguments[1]);
                    string right = (string)globalFilter;
                    globalFilter = new Tangosol.Util.Extractor.ChainedExtractor(right);
                    return m;
                }
            }
            throw new NotSupportedException(string.Format("The method '{0}' is not supported", m.Method.Name));
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            switch (u.NodeType)
            {
                case ExpressionType.Not:
                    this.Visit(u.Operand);
                    globalFilter = new NotFilter((IFilter)globalFilter);
                    break;
                case ExpressionType.Convert:
                    this.Visit(u.Operand);
                    try
                    {
                        globalFilter = Convert.ChangeType(globalFilter, u.Type);
                    }
                    catch (InvalidCastException)
                    {
                    }
                    break;
                default:
                    throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
            }
            return u;
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            this.Visit(b.Left);
            object lastGlobal1 = globalFilter;
            this.Visit(b.Right); 
            object lastGlobal2 = globalFilter;
            switch (b.NodeType)
            {
                case ExpressionType.AndAlso:
                    globalFilter = new AndFilter((IFilter) lastGlobal1, (IFilter)lastGlobal2);
                    break;
                case ExpressionType.OrElse:
                    globalFilter = new OrFilter((IFilter) lastGlobal1, (IFilter)lastGlobal2); 
                    break;
                case ExpressionType.Equal:
                    if (lastGlobal1 is string)
                        globalFilter = new EqualsFilter((string)lastGlobal1, lastGlobal2);
                    else
                        globalFilter = new EqualsFilter((IValueExtractor)lastGlobal1, lastGlobal2);
                    break;
                case ExpressionType.NotEqual:
                    if (lastGlobal1 is string)
                        globalFilter = new NotEqualsFilter((string)lastGlobal1, lastGlobal2);
                    else
                        globalFilter = new NotEqualsFilter((IValueExtractor)lastGlobal1, lastGlobal2);
                    break;
                case ExpressionType.LessThan:
                    if (lastGlobal1 is string)
                        globalFilter = new LessFilter((string)lastGlobal1, (IComparable)lastGlobal2);
                    else
                        globalFilter = new GreaterFilter((IValueExtractor)lastGlobal1, (IComparable)lastGlobal2);
                    break;
                case ExpressionType.LessThanOrEqual:
                    if (lastGlobal1 is string)
                        globalFilter = new LessEqualsFilter((string)lastGlobal1, (IComparable)lastGlobal2);
                    else
                        globalFilter = new GreaterFilter((IValueExtractor)lastGlobal1, (IComparable)lastGlobal2);
                    break;
                case ExpressionType.GreaterThan:
                    if (lastGlobal1 is string)
                        globalFilter = new GreaterFilter((string)lastGlobal1, (IComparable)lastGlobal2);
                    else
                        globalFilter = new GreaterFilter((IValueExtractor)lastGlobal1, (IComparable)lastGlobal2);
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    if (lastGlobal1 is string)
                        globalFilter = new GreaterEqualsFilter((string)lastGlobal1, (IComparable)lastGlobal2);
                    else
                        globalFilter = new GreaterEqualsFilter((IValueExtractor)lastGlobal1, (IComparable)lastGlobal2);
                    break;
                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
            }
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            IQueryable q = c.Value as IQueryable;
            if (q != null)
            {
                // assume constant nodes w/ IQueryables are table references
                //sb.Append("SELECT * FROM ");
                //sb.Append(q.ElementType.Name);
            }
            else if (c.Value == null)
            {
                globalFilter = c.Value;
            }
            else
            {
                switch (Type.GetTypeCode(c.Value.GetType()))
                {
                    case TypeCode.Boolean:
                        globalFilter = c.Value;
                        break;
                    case TypeCode.String:
                        globalFilter = c.Value;
                        break;
                    case TypeCode.Object:
                        //throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
                        globalFilter = c.Value;
                        break;
                    default:
                        globalFilter = c.Value;
                        break;
                }
            }
            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
            {
                globalFilter = "get" + m.Member.Name ;
                return m;
            }
            throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
        }
    }
}